﻿#region Using directives

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

using Weazel.Math;

#endregion

namespace Weazel.Gezel.Simulator.Reference.IpBlocks
{
  /// <summary>
  /// Motivated by the microcontrol store of Andrew Tanenbaum's Mic-1 in his
  /// book Structured Computer Organization.  It offers the functionality of
  /// a read-only memory with an initial contents taken from a file with
  /// its name provided as an ipparm "file = ...".  This will typically be
  /// a binary file with its first four bytes forming a 'magic-number' that
  /// will be skipped.
  /// 
  /// Author: Andreas Vad Lorenzen, modified by Jørgen Steensgaard-Madsen
  ///         (original c version from Gezel 1.5)
  /// 
  /// Ported to Weazel by Peter Sørensen & Per Larsen
  /// 
  /// </summary>
  [IpBlockAttribute("rom")]
  public class RomBlock : AbstractIpBlock
  {
    List<Integer> content = new List<Integer>();

    int wordLength;
    int size;
    int startbyte = 4;
    bool hasStartByte = false;
    string filename;

		public RomBlock(string name)
			: base(name)
    {

    }

    public override bool CannotSleepTest()
    {
      return false; // ram can always cause 'sleep' mode
    }

    public override void SetParameter(string name)
    {
      string field = name.Substring(0, name.IndexOf('='));
      string value = name.Substring(name.IndexOf('=') + 1);

      field = field.Trim();
      value = value.Trim();

      switch (field)
      {
        case "wl":
          this.wordLength = int.Parse(value);
          Console.WriteLine("rom: set wl {0}", value);
          break;
        case "size":
          this.size = int.Parse(value);
          Console.WriteLine("rom: set size {0}", value);
          break;
        case "file":
          this.filename = value.Trim();
          Console.WriteLine("rom: set file {0}", filename);
          break;
        case "startbyte":
          this.startbyte = int.Parse(value);
          this.hasStartByte = true;
          Console.WriteLine("rom: set startbyte {0}", startbyte);
          break;
        default:
          throw new InvalidOperationException("invalid parameter '" + field + "'");
      }

      // if we have all parameters set exept startbyte
      // we can initialize its contents
      if (wordLength != 0 && size != 0 && filename != null && hasStartByte)
      {
        InitializeRom();
      }
    }

    private void InitializeRom()
    {
      int max = ((wordLength + 7) / 8) * size + startbyte;

      byte[] buffer = new byte[max];

      FileStream stream = null;

      try
      {
        stream = File.Open(filename, FileMode.Open);
      }
      catch (FileNotFoundException e)
      {

        throw new IpBlockFileNotFoundException(e, this);
      }

      content.Clear();

      int bytesRead = stream.Read(buffer, 0, max);

      stream.Close();

      long position = startbyte;    
      long startPosition = (wordLength + 7) / 8;

      // the following is quite different from the original c version ..
      while (position < max)
      {
        // read a long (64 bits)
        long value = 0;
        for (long j = startPosition, k = 7; j > 0 && position < max; j--, k--)
        {
          long shift = ((long)buffer[position++] << (int)(8 * k));
          value = shift | value;
        }

        value = value >> (64 - wordLength);        

        content.Add(new Integer(wordLength, value, false));
      }
    }

    public override bool CheckTerminal(int n, string terminal, AbstractIpBlock.IoDirection direction)
    {
      switch (n)
      {
        case 0:
          return (direction == AbstractIpBlock.IoDirection.Input && terminal == "address");
        case 1:
          return (direction == AbstractIpBlock.IoDirection.Input && terminal == "rd");
        case 2:
          return (direction == AbstractIpBlock.IoDirection.Output && terminal == "odata");
        case 3:
          return (direction == AbstractIpBlock.IoDirection.Output && terminal == "startbyte");
      }
      return false;
    }

    public override void Run()
    {
      // perform rom function using 
      //   io vector       #
      //   -----------------------------
      //   in address      0
      //   in rd           1
      //   out odata       2
      if (ioValues[1].ToInt() == 1)
      {
        if (ioValues[0].ToInt() > content.Count - 1)
        {
          throw new ApplicationException(
          string.Format("Error: Address value for rom ip block out-of-range - {0} > {1}", ioValues[0].ToString(), content.Count - 1));
        }
        else
        {
          // is a read
          ioValues[2].ValueCopy(content[ioValues[0].ToInt()]);
        }
      }
    }
  }
}
